﻿// <copyright file="Program.cs" company="Tom Ballard">
// Copyright (c) Tom Ballard.  All rights reserved.
// </copyright>

namespace XmlTool
{
    #region Using directives
    using System;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Xml;
    using System.Xml.Xsl;
    using XmlTool.Properties;
    #endregion

    /// <summary>
    /// XML command line tool.
    /// </summary>
    internal static class Program
    {
        /// <summary>
        /// Program entry point.
        /// </summary>
        /// <param name="args">The arguments.</param>
        /// <returns>The return value.</returns>
        private static int Main(string[] args)
        {
            int result = CommandLine.Run(
            () =>
            {
                var c = new CommandLine(args);
                Action action = null;
                Func<bool> validate = null;
                int force = 0;
                int? indent = null;
                string[] file = null;
                Func<int, bool> validateFile = n =>
                {
                    file = c.File.ToArray();
                    int expected = n + ((force == 2) ? 1 : 0);
                    bool ok = file.Length == expected;
                    if (ok && (force == 1))
                    {
                        ok = !CommandLine.IsStandardIO(file.Last());
                    }

                    return ok;
                };
                foreach (var i in c.Option)
                {
                    switch (c.List[i])
                    {
                        case "-d":
                        case "--debug":
                            if (!CommandLine.HandleDebug())
                            {
                                c.ShowOptionError(i);
                                goto Help;
                            }

                            break;

                        case "-p":
                        case "--pretty":
                            if (!object.ReferenceEquals(action, null))
                            {
                                c.ShowOptionError(i);
                                goto Help;
                            }

                            validate = () => validateFile(2);
                            action = () =>
                            {
                                Indent(file[0], file[1]);
                            };
                            break;

                        case "-t":
                        case "--transform":
                            if (!object.ReferenceEquals(action, null))
                            {
                                c.ShowOptionError(i);
                                goto Help;
                            }

                            validate = () => validateFile(3);
                            action = () =>
                            {
                                Transform(file[0], file[1], file[2]);
                            };
                            break;

                        case "-v":
                        case "--validate":
                            if (!object.ReferenceEquals(action, null))
                            {
                                c.ShowOptionError(i);
                                goto Help;
                            }

                            validate = () => validateFile(3);
                            action = () =>
                            {
                                Validate(file[0], file[1], file[2]);
                            };
                            break;

                        case "-f|--force":
                            if (2 == force)
                            {
                                c.ShowOptionError(i);
                                goto Help;
                            }

                            force++;
                            break;

                        case "-i":
                        case "--indent":
                            if (c.AddFlag("-f", i + 1, 1))
                            {
                                int parsed;
                                if (!int.TryParse(
                                    c.List[i + 1],
                                    NumberStyles.None,
                                    CultureInfo.InvariantCulture,
                                    out parsed) || (parsed < 0))
                                {
                                    c.ShowOptionError(i + 1);
                                    goto Help;
                                }

                                indent = parsed;
                            }
                            else
                            {
                                c.ShowOptionError(i);
                                goto Help;
                            }

                            break;

                        default:
                            c.ShowOptionError(i);
                            goto Help;
                    }
                }

                if (object.ReferenceEquals(action, null) ||
                    !(object.ReferenceEquals(validate, null) || validate()))
                {
                    goto Help;
                }

                action();
                return CommandLine.SuccessResult;
            Help:
                CommandLine.ShowHelp(
                (err, exe) =>
                {
                    err.WriteLine("{0} -p|--pretty [-f] [-ff] [-i <n>] [-d] <in> <out>", exe);
                    err.WriteLine("  Pretty print <in> to <out>.");
                    err.WriteLine("  -i: Indent <n> spaces (default is tab.).");
                    err.WriteLine("{0} -t|--transform [-f] [-ff] [-d] <xslt> <in> <out>", exe);
                    err.WriteLine("  Transform <in> to <out> using <xslt>.");
                    err.WriteLine("{0} -v|--validate [-f] [-ff] [-d] <xsd> <in> <out>", exe);
                    err.WriteLine("  Validate <in> to <out> using <xsd>.");
                    err.WriteLine("-f: Overwrite <out>.");
                    err.WriteLine("-ff: Overwrite <out> in place.");
                    err.WriteLine(CommandLineStrings.DebugHelp);
                    err.WriteLine(CommandLineStrings.StandardIoHelp);
                });
                return CommandLine.ShowHelpResult;
            });
            return result;
        }

        /// <summary>
        /// Indents the specified in.
        /// </summary>
        /// <param name="in">The in.</param>
        /// <param name="out">The out.</param>
        private static void Indent(string @in, string @out)
        {
            var type = typeof(Program);
            Stream transformStream = null;
            try
            {
                transformStream = type.Assembly.GetManifestResourceStream(
                    type.Namespace + ".Properties.PrettyPrint.xslt");
                using (var transformReader = new StreamReader(transformStream))
                {
                    Transform(transformReader, @in, @out);
                }
            }
            finally
            {
                if (!object.ReferenceEquals(transformStream, null))
                {
                    transformStream.Dispose();
                }
            }
        }

        /// <summary>
        /// Transforms the specified transform.
        /// </summary>
        /// <param name="transform">The transform.</param>
        /// <param name="in">The in.</param>
        /// <param name="out">The out.</param>
        private static void Transform(string transform, string @in, string @out)
        {
            CommandLine.UseReader(
            transform,
            transformReader =>
            {
                Transform(transformReader, @in, @out);
            });
        }

        /// <summary>
        /// Transforms the specified transform reader.
        /// </summary>
        /// <param name="transformReader">The transform reader.</param>
        /// <param name="in">The in.</param>
        /// <param name="out">The out.</param>
        private static void Transform(TextReader transformReader, string @in, string @out)
        {
            var transform = new XslCompiledTransform();
            var transformSettings = new XmlReaderSettings();
            using (var xmlTransform = XmlReader.Create(transformReader, transformSettings))
            {
                transform.Load(xmlTransform);
            }

            CommandLine.UseReader(
            @in,
            textReader =>
            {
                var readerSettings = new XmlReaderSettings();
                using (var xmlReader = XmlReader.Create(textReader, readerSettings))
                {
                    CommandLine.UseWriter(
                    @out,
                    textWriter =>
                    {
                        var writerSettings = new XmlWriterSettings();
                        using (var xmlWriter = XmlWriter.Create(textWriter, writerSettings))
                        {
                            transform.Transform(xmlReader, xmlWriter);
                        }
                    });
                }
            });
        }

        /// <summary>
        /// Validates the specified schema.
        /// </summary>
        /// <param name="schema">The schema.</param>
        /// <param name="in">The in.</param>
        /// <param name="out">The out.</param>
        private static void Validate(string schema, string @in, string @out)
        {
        }
    }
}
